<!DOCTYPE html>
<html>
  <style></style>
  <body>
    <div id="app"></div>
  </body>
  <script type="text/javascript">
    /**
     * 内置组件翻译器
     */
    class BuiltInComponentTranslator {
      render(component) {
        throw new Error('render method not implements ... ');
      }
    }

    class TextTranslator extends BuiltInComponentTranslator {
      render(component) {
        let element = component._renderElement;
        if (!element) {
          // text 元素如果组件已经渲染则直接更新组件属性即可
          element = document.createElement('span');
        }
        element.innerText = component.text;
        return element;
      }
    }

    class ImageTranslator extends BuiltInComponentTranslator {
      render(component) {
        let element = component._renderElement;
        if (!element) {
          // image 元素如果组件已经渲染则直接更新组件属性即可
          element = document.createElement('img');
        }
        element.src = component.src;
        return element;
      }
    }

    class BaseContainerTranslator extends BuiltInComponentTranslator {
      render(component) {
        let element = component._renderElement;
        if (!element) {
          // image 元素如果组件已经渲染则直接更新组件属性即可
          element = document.createElement('div');
        }
        component.children.forEach((child, index) => {
          const childElement = child.render();
          element.appendChild(childElement);
        });
        return element;
      }
    }

    const TRANSLATOR_MAP = {
      Text: new TextTranslator(),
      Image: new ImageTranslator(),
      Container: new BaseContainerTranslator(),
    };

    class Commpoent {
      constructor() {
        // 实际渲染元素
        this._renderElement = null;
      }
      render() {
        const translator = TRANSLATOR_MAP[this.name];
        if (translator) {
          // 如果有内置的翻译器则直接由翻译器翻译
          this._renderElement = translator.render(this);
          return this._renderElement;
        }
        throw new Error('method not implements ... ');
      }

      // 更新视图
      _update() {
        this.render();
      }

      get name() {
        throw new Error('method not implements ... ');
      }
    }

    class TextComponent extends Commpoent {
      constructor() {
        super();
        this._text = '';
      }

      get text() {
        return this._text;
      }

      set text(_text) {
        if (typeof _text === 'undefined') {
          _text = '';
        }
        const newText = _text + '';
        if (newText !== this._text) {
          this._text = newText;
          this._update();
        }
      }

      get name() {
        return 'Text';
      }
    }

    class ImageCompnent extends Commpoent {
      constructor() {
        super();
        this._src = '';
      }

      get src() {
        return this._src;
      }

      set src(_src) {
        if (_src) {
          this._src = _src + '';
        }
      }

      get name() {
        return 'Image';
      }
    }

    class BaseContainerComponent extends Commpoent {
      constructor() {
        super();
        this._children = [];
      }

      add(component) {
        if (component && component instanceof Commpoent) {
          this._children.push(component);
        }
      }

      get children() {
        return this._children;
      }

      get name() {
        return 'Container';
      }
    }

    const COMPONENT_MAP = {
      Text: TextComponent,
      Image: ImageCompnent,
      Container: BaseContainerComponent,
    };

    class ExpressableCompnent extends Commpoent {
      constructor(_referObject) {
        super();
        this._express = '';
        this._referObject = null;
        this._referComponent = null;
        this.referObject = _referObject;
      }

      get express() {
        return this._express;
      }

      set express(_express) {
        if (!_express) {
          return;
        }

        this._express = _express + '';
        let object = null;
        try {
          object = JSON.parse(this._express);
        } catch (e) {}
        if (!object) {
          return;
        }
        this.referObject = object;
      }

      set referObject(_referObject) {
        let object = _referObject;
        if (!object || !object.type || !COMPONENT_MAP[object.type]) {
          return;
        }

        this._referObject = _referObject;
        this._referComponent = new COMPONENT_MAP[object.type]();
        for (let key in object) {
          if (key === 'type') {
            continue;
          }
          if (object.hasOwnProperty(key)) {
            console.log(key + ': ' + object[key]);
            if ('children' === key && this._referComponent['add']) {
              const children = object[key];
              children.forEach((child, index) => {
                const childComponent = new ExpressableCompnent(child);
                this._referComponent.add(childComponent._referComponent);
              });
              continue;
            }
            this._referComponent[key] = object[key];
          }
        }
      }

      render() {
        return this._referComponent.render();
      }
    }

    class App {
      constructor() {
        this._root = null;
      }

      set rootComponent(_root) {
        if (_root && _root instanceof Commpoent) {
          this._root = _root;
        }
      }

      bind(_appElement) {
        _appElement.appendChild(this._root.render());
      }
    }

    const rootComponent = new BaseContainerComponent();

    const textComponent = new TextComponent();
    textComponent.text =
      '快科技6月18日消息，据国内媒体报道，2020年，脑瘫少年姚俊鹏高考分超过理科一本线108分，以623分的成绩考上中国药科大学。';

    const impageComponent = new ImageCompnent();
    impageComponent.src =
      'https://img-s-msn-com.akamaized.net/tenant/amp/entityid/BB1oqqzJ.img?w=640&h=426&m=6';

    const expressableCompnent = new ExpressableCompnent();
    expressableCompnent.express = `
        {
          "type": "Container",
          "children": [{
              "type": "Image",
              "src": "https://img-s-msn-com.akamaized.net/tenant/amp/entityid/BB1oqqzJ.img?w=640&h=426&m=6"
            },
            {
              "type": "Image",
              "src": "https://img-s-msn-com.akamaized.net/tenant/amp/entityid/BB1oqqzJ.img?w=640&h=426&m=6"
            },
            {
              "type": "Container",
              "children": [{
                  "type": "Text",
                  "text": "https://img-s-msn-com.akamaized.net/tenant/amp/entityid/BB1oqqzJ.img?w=640&h=426&m=6"
                },
                {
                  "type": "Image",
                  "src": "https://img-s-msn-com.akamaized.net/tenant/amp/entityid/BB1oqqzJ.img?w=640&h=426&m=6"
                }
              ]
            }
          ]
        }
        `;

    const customerComplexComponentModelDemo1 = {
      name: 'UserInfoComponent',
      props: [
        {
          name: 'userInfo',
          type: 'UserDataItem',
          scope: 'public',
        },
      ],
      actions: [
        {
          scope: 'public',
          name: 'setUserInfo',
          args: [
            { name: 'name', type: 'String', description: 'user name' },
            { name: 'age', type: 'number', description: 'user age' },
            {
              name: 'avator',
              type: 'String',
              description: 'user avator image src ',
            },
          ],
          returnType: {
            type: 'void',
          },
          implmentLogic: [
            { type: 'simple', logic: '$userInfo.name = args.name' },
            { type: 'simple', logic: '$userInfo.age = args.age' },
            {
              type: 'if',
              condition: '$age < 18',
              subLogic: [
                {
                  type: 'simple',
                  logic: '$log.info("age is less than 18 ")',
                },
              ],
            },
            {
              type: 'simple',
              logic:
                '$emit("onUserInfoChnage", new UserDataItemChangeEvent($userInfo))',
            },
          ],
        },
        {
          scope: 'public',
          name: 'getUserInfo',
          args: [],
          returnType: {
            type: 'UserDataItem',
          },
          implmentLogic: [
            {
              type: 'simple',
              logic: 'return $userInfo',
            },
          ],
        },

        {
          scope: 'private',
          name: 'userImageLoadHandle',
          args: [],
          returnType: {
            type: 'void',
          },
          implmentLogic: [
            {
              type: 'simple',
              logic: '$log(`component root handle event of sub component `)',
            },
          ],
        },
      ],

      events: [
        {
          scope: 'public',
          name: 'onUserInfoChange',
          args: [
            {
              name: 'event',
              type: 'UserDataItemChangeEvent',
              description: 'event param payload',
            },
          ],
        },
      ],

      layout: {
        type: 'Container',
        children: [
          {
            type: 'Text',
            name: 'userNameText',
            propBind: [{ name: 'text', srouce: 'userInfo.name' }],
          },
          {
            type: 'Text',
            name: 'userAgeText',
            propBind: [{ name: 'text', srouce: 'userInfo.age' }],
          },
          {
            type: 'Image',
            name: 'userAvatorImage',
            propBind: [{ name: 'src', srouce: 'userInfo.avator' }],
            eventBind: [
              {
                name: 'onLoad',
                logic: [
                  {
                    type: 'simple',
                    logic: '$log.info(`${$$.name} image load success ... `)',
                  },
                  { type: 'simple', logic: '$userImageLoadHandle()' },
                ],
              },
            ],
          },
        ],
      },
    };

    const customerComplexComponentDataDemo1 = {
      props: {
        userInfo: {
          name: '张三',
          age: 18,
          avator:
            'https://img-s-msn-com.akamaized.net/tenant/amp/entityid/BB1oqqzJ.img?w=640&h=426&m=6',
        },
      },
    };
    /**
     * 客制化 复合 组件
     */
    class CustomerComplexComponentBuilder {
      build(model, data) {
        // 创建对象
        const _Class = this._createClassFromJson(model, data);
        const component = new _Class();
        if (data) {
          component.bindProps(data.props);
        }
        return component;
      }

      /**
       * 创建动态组件类
       */
      _createClassFromJson(model) {
        const DynamicClass = class extends ExpressableCompnent {
          constructor() {
            super(null);
            this._model = model;
            this._dynamicExpressObject = this._createExpressObject(
              this._model.layout
            );
          }

          get name() {
            return this._model.name;
          }
          _createExpressObject(layout) {
            const expressObject = {};
            expressObject.type = layout.type;
            if (layout.children) {
              expressObject.children = [];
              layout.children.forEach((child, index) => {
                if (child) {
                  expressObject.children.push(this._createExpressObject(child));
                }
              });
            }
            layout.expressObject = expressObject;
            return expressObject;
          }

          /**
           * 绑定
           */
          bindProps(props) {
            this._bindProps(this._model.layout, props);
            this.referObject = this._dynamicExpressObject;
          }

          _bindProps(layout, props) {
            if (!props) {
              return;
            }
            if (layout.propBind && layout.expressObject) {
              layout.propBind.forEach((propBindItem, index) => {
                if (propBindItem && propBindItem.name && propBindItem.srouce) {
                  const sourseItems = propBindItem.srouce.split('.');
                  let targetObject = null;
                  for (let i = 0; i < sourseItems.length; i++) {
                    const sourseItem = sourseItems[i];
                    if (targetObject == null) {
                      targetObject = props[sourseItem];
                      if (typeof targetObject === 'undefined') {
                        break;
                      }
                    } else {
                      targetObject = targetObject[sourseItem];
                      if (typeof targetObject === 'undefined') {
                        break;
                      }
                    }
                  }
                  if (typeof targetObject !== 'undefined') {
                    layout.expressObject[propBindItem.name] = targetObject;
                  }
                }
              });
            }
            if (layout.children) {
              layout.children.forEach((child, index) => {
                if (child) {
                  this._bindProps(child, props);
                }
              });
            }
          }
        };
        return DynamicClass;
      }
    }

    const builder = new CustomerComplexComponentBuilder();

    rootComponent.add(textComponent);
    rootComponent.add(impageComponent);

    // rootComponent.add(expressableCompnent);

    const userInfoComponent = builder.build(
      customerComplexComponentModelDemo1,
      customerComplexComponentDataDemo1
    );

    // debugger;

    rootComponent.add(userInfoComponent);

    const app = new App();
    app.rootComponent = rootComponent;

    let appElement = document.getElementById('app');
    app.bind(appElement);

    let i = 0;
    setInterval(() => {
      console.log('update test ... ', userInfoComponent);
      textComponent.text = 'test labal' + i++;
    }, 500);
  </script>
</html>
